﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Data;


namespace CdgNetPersistencia.Componentes
{
    /// <summary>
    /// Clase de ejecucion de una insercion masiva
    /// </summary>
    public class SQLiteBulkInsert
    {

        #region PROPIEDADES DE LA CLASE

        private SQLiteConnection __conConexion;
        private SQLiteCommand __cmdComando;
        private SQLiteTransaction __trxTransaccion;

        private Dictionary<string, SQLiteParameter> __dicParametros = new Dictionary<string, SQLiteParameter>();

        private uint __nContador = 0;
        private string __cSentenciaInsertInicial;

        private uint __nMaximoCommit = 10000;
        public uint Maximo_commit { get { return __nMaximoCommit; } set { __nMaximoCommit = value; } }

        private string __cNombreTabla;
        public string Nombre_tabla { get { return __cNombreTabla; } }

        private string __cDelimitadorParam = ":";
        public string Delimitador_parametros { get { return __cDelimitadorParam; } }

        private bool __bPermitirBulkInsert = true;
        public bool Permitir_bulk_insert { get { return __bPermitirBulkInsert; } set { __bPermitirBulkInsert = value; } }


        #endregion


        #region CONSTRUTORES DE LA CLASE

        /// <summary>
        /// Constructor de la clase
        /// </summary>
        /// <param name="conConexionParam">Instancia de la conexion al DBF SQLite</param>
        /// <param name="cNombreTablaParam">Nombre de la tabla a la cual insertar</param>
        public SQLiteBulkInsert(SQLiteConnection conConexionParam, string cNombreTablaParam)
        {
            //asignamos los valores de PROPIEDADES
            __conConexion = conConexionParam;
            __cNombreTabla = cNombreTablaParam;

            //ensamblamos el inicio de la sentencia de insercion
            StringBuilder cSentenciaSQL = new StringBuilder(255);
            cSentenciaSQL.Append("INSERT INTO ["); cSentenciaSQL.Append(cNombreTablaParam); cSentenciaSQL.Append("] (");

            //la establecemos
            __cSentenciaInsertInicial = cSentenciaSQL.ToString();

        }

        
        
        #endregion


        #region METODOS DE LA CLASE

        /// <summary>
        /// Devuelve el comando a ejecutar
        /// </summary>
        public string CommandText
        {
            get
            {
                if (__dicParametros.Count < 1)
                    throw new SQLiteException("Debes agregar al menos un parametro.");

                StringBuilder sb = new StringBuilder(255);
                sb.Append(__cSentenciaInsertInicial);

                foreach (string param in __dicParametros.Keys)
                {
                    sb.Append('[');
                    sb.Append(param);
                    sb.Append(']');
                    sb.Append(", ");
                }
                sb.Remove(sb.Length - 2, 2);

                sb.Append(") VALUES (");

                foreach (string param in __dicParametros.Keys)
                {
                    sb.Append(__cDelimitadorParam);
                    sb.Append(param);
                    sb.Append(", ");
                }
                sb.Remove(sb.Length - 2, 2);

                sb.Append(")");

                //devolvemos el resultado del metodo
                return sb.ToString();

            }
        }

        /// <summary>
        /// Agrega un parametro a la lista de parametros del comando
        /// </summary>
        /// <param name="cNombreParam">Nombre del parametro</param>
        /// <param name="tTipoDatosParam">Tipo de datos del parametro</param>
        public void AddParameter(string cNombreParam, DbType tTipoDatosParam)
        {
            SQLiteParameter param = new SQLiteParameter(__cDelimitadorParam + cNombreParam, tTipoDatosParam);
            __dicParametros.Add(cNombreParam, param);

        }

        /// <summary>
        /// Confirma la transaccion actualmente pendiente
        /// </summary>
        public void Flush()
        {
            try
            {
                //si hay una transaccion activa, la confimamos
                if (__trxTransaccion != null) __trxTransaccion.Commit();
            }
            catch (Exception ex) { throw new Exception("No se puede confirmar la transacción. See InnerException for more details", ex); }
            finally
            {
                //si existe la instancia de la transaccion, la liberamos de la memoria
                if (__trxTransaccion != null) __trxTransaccion.Dispose();

                //reseteamos las demas instancias
                __trxTransaccion = null;
                __nContador = 0;
            }
        }

        /// <summary>
        /// Ejecuta la insercion de un registro con los valores de parametro pasados
        /// </summary>
        /// <param name="aValoresParametrosParam">Arreglo de valores de parametros a insertar</param>
        public void Insert(object[] aValoresParametrosParam)
        {
            //si la cantidad de nombres de parametros difiere de la cantidad de valores a asignarles
            if (aValoresParametrosParam.Length != __dicParametros.Count)
                //mensaje de excepcion
                throw new Exception("La cantidad de valores del arreglo debe ser igual a la cantidad de parametros del comando!.");

            //incrementamos el contador de registros
            __nContador++;

            //si es el primer registro
            if (__nContador.Equals(1))
            {
                //si se permite la insercion masiva
                if (__bPermitirBulkInsert)
                    //iniciamos una transaccion
                    __trxTransaccion = __conConexion.BeginTransaction();

                //creamos una nueva instancia de comando
                __cmdComando = __conConexion.CreateCommand();

                //recorremos la lista de valores de parametros
                foreach (SQLiteParameter par in __dicParametros.Values)
                    //le agregamos el parametro al comando
                    __cmdComando.Parameters.Add(par);

                //establecemos la sentencia del comando
                __cmdComando.CommandText = this.CommandText;

            }

            //un contador auxiliar
            int i = 0;

            //recorremos el diccionario de parametros
            foreach (SQLiteParameter par in __dicParametros.Values)
            {
                //le asignamos el valor al parametro
                par.Value = aValoresParametrosParam[i];

                //incrementamos el contador auxiliar
                i++;
            }

            //ejecutamos el comando
            __cmdComando.ExecuteNonQuery();

            //si el registro actual es igual al limite maximo para ejecutar una confirmacion
            if (__nContador == __nMaximoCommit)
            {
                try
                {
                    //si hay una transaccion activa
                    if (__trxTransaccion != null)
                        //la confirmamos
                        __trxTransaccion.Commit();
                }
                catch (Exception ex) { }
                finally
                {
                    //si existe la instancia de la transaccion, la liberamos de la memoria
                    if (__trxTransaccion != null) __trxTransaccion.Dispose();

                    //reseteamos las demas instancias
                    __trxTransaccion = null;
                    __nContador = 0;

                }
            }
        }


        #endregion

        
    }
}
